#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Logger.py
'''

__author__ = 'lyj'


import sys
import os
import time
import glob
import codecs
import string
import logging
import logging.handlers
import fnmatch

from logging import StreamHandler
from logging.handlers import BaseRotatingHandler, TimedRotatingFileHandler

import logging
from logging.handlers import SMTPHandler, TimedRotatingFileHandler

#logging.basicConfig(level = logging.DEBUG)
#trfh = TimedRotatingFileHandler("testing.rotating_log", "D", 7)
#logging.getLogger("testing.hex").addHandler(trfh)


# ********************************
# START 重写FileHandler
# ********************************
class DailyFileHandler(logging.FileHandler):
    def __init__(self, filename, mode='a', encoding=None):
        """
        Open the specified file and use it as the stream for logging.
        """
        if codecs is None:
            encoding = None
        suffix = "%Y-%m-%d"
        timeTuple = time.localtime(time.time())
        dfn = filename + '.' + time.strftime(suffix, timeTuple)
        if encoding is None:
            stream = open(dfn, mode)
        else:
            stream = codecs.open(dfn, mode, encoding)
        StreamHandler.__init__(self, stream)
        #keep the absolute path, otherwise derived classes which use this
        #may come a cropper when the current directory changes
        self.baseFilename = os.path.abspath(filename)
        self.mode = mode

class DailyBaseRotatingHandler(BaseRotatingHandler, DailyFileHandler):
    def __init__(self, filename, mode, encoding=None):
        if codecs is None:
            encoding = None
        DailyFileHandler.__init__(self, filename, mode, encoding)
        self.mode = mode
        self.encoding = encoding

class DailyTimedRotatingFileHandler(TimedRotatingFileHandler, DailyBaseRotatingHandler):
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None):
        DailyBaseRotatingHandler.__init__(self, filename, 'a', encoding)
        self.when = string.upper(when)
        self.backupCount = backupCount
        # Calculate the real rollover interval, which is just the number of
        # seconds between rollovers.  Also set the filename suffix used when
        # a rollover occurs.  Current 'when' events supported:
        # S - Seconds
        # M - Minutes
        # H - Hours
        # D - Days
        # midnight - roll over at midnight
        # W{0-6} - roll over on a certain day; 0 - Monday
        #
        # Case of the 'when' specifier is not important; lower or upper case
        # will work.
        currentTime = int(time.time())
        if self.when == 'S':
            self.interval = 1 # one second
            self.suffix = "%Y-%m-%d_%H-%M-%S"
        elif self.when == 'M':
            self.interval = 60 # one minute
            self.suffix = "%Y-%m-%d_%H-%M"
        elif self.when == 'H':
            self.interval = 60 * 60 # one hour
            self.suffix = "%Y-%m-%d_%H"
        elif self.when == 'D' or self.when == 'MIDNIGHT':
            self.interval = 60 * 60 * 24 # one day
            self.suffix = "%Y-%m-%d"
        elif self.when.startswith('W'):
            self.interval = 60 * 60 * 24 * 7 # one week
            if len(self.when) != 2:
                raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
            if self.when[1] < '0' or self.when[1] > '6':
                raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
            self.dayOfWeek = int(self.when[1])
            self.suffix = "%Y-%m-%d"
        else:
            raise ValueError("Invalid rollover interval specified: %s" % self.when)

        self.interval = self.interval * interval # multiply by units requested
        self.rolloverAt = currentTime + self.interval

        # If we are rolling over at midnight or weekly, then the interval is already known.
        # What we need to figure out is WHEN the next interval is.  In other words,
        # if you are rolling over at midnight, then your base interval is 1 day,
        # but you want to start that one day clock at midnight, not now.  So, we
        # have to fudge the rolloverAt value in order to trigger the first rollover
        # at the right time.  After that, the regular interval will take care of
        # the rest.  Note that this code doesn't care about leap seconds. :)
        if self.when == 'MIDNIGHT' or self.when.startswith('W'):
            # This could be done with less code, but I wanted it to be clear
            t = time.localtime(currentTime)
            currentHour = t[3]
            currentMinute = t[4]
            currentSecond = t[5]
            # r is the number of seconds left between now and midnight
            r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
                    currentSecond)
            self.rolloverAt = currentTime + r
            # If we are rolling over on a certain day, add in the number of days until
            # the next rollover, but offset by 1 since we just calculated the time
            # until the next day starts.  There are three cases:
            # Case 1) The day to rollover is today; in this case, do nothing
            # Case 2) The day to rollover is further in the interval (i.e., today is
            #         day 2 (Wednesday) and rollover is on day 6 (Sunday).  Days to
            #         next rollover is simply 6 - 2 - 1, or 3.
            # Case 3) The day to rollover is behind us in the interval (i.e., today
            #         is day 5 (Saturday) and rollover is on day 3 (Thursday).
            #         Days to rollover is 6 - 5 + 3, or 4.  In this case, it's the
            #         number of days left in the current week (1) plus the number
            #         of days in the next week until the rollover day (3).
            # The calculations described in 2) and 3) above need to have a day added.
            # This is because the above time calculation takes us to midnight on this
            # day, i.e. the start of the next day.
            if when.startswith('W'):
                day = t[6] # 0 is Monday
                if day != self.dayOfWeek:
                    if day < self.dayOfWeek:
                        daysToWait = self.dayOfWeek - day
                    else:
                        daysToWait = 6 - day + self.dayOfWeek + 1
                    self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))

        #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)

class LoggingTimedRotatingFileHandler(DailyTimedRotatingFileHandler):

    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None):
        DailyTimedRotatingFileHandler.__init__(self, filename, when, interval, backupCount, encoding)

    def doRollover(self):
        """
        do a rollover; in this case, a date/time stamp is appended to the filename
        when the rollover happens.  However, you want the file to be named for the
        start of the interval, not the current time.  If there is a backup count,
        then we have to get a list of matching filenames, sort them and remove
        the one with the oldest suffix.
        """
        self.stream.close()
        # get the time that this sequence started at and make it a TimeTuple
        t = self.rolloverAt - self.interval
        timeTuple = time.localtime(time.time())
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        #if os.path.exists(dfn):
        #    os.remove(dfn)
        #os.rename(self.baseFilename, dfn)
        if self.backupCount > 0:
            # find the oldest log file and delete it
            s = glob.glob(self.baseFilename + ".20*")
            if len(s) > self.backupCount:
                s.sort()
                os.remove(s[0])
        #print "%s -> %s" % (self.baseFilename, dfn)
        if self.encoding:
            self.stream = codecs.open(dfn, 'w', self.encoding)
        else:
            self.stream = open(dfn, 'w')
        self.rolloverAt = self.rolloverAt + self.interval
# ********************************
# End 重写FileHandler
# ********************************

def funcToMethod(func, clas, method_name=None):
    setattr(clas, method_name or func.__name__, func)

def exception(self, message):
    self.log(logging.EXCEPTION, message)

def veryverbose(self, message):
    self.log(logging.VERYVERBOSE, message)

def veryveryverbose(self, message):
    self.log(logging.VERYVERYVERBOSE, message)
    
class Logger:

    def __init__(self, logname, log_level, loggername, bytes=False):

        # Standard error is redirected in the log
        # FIXME: Potential problem when rotating occurs
        #if not fnmatch.fnmatch(logname, '*.log.notb'):
        #    sys.stderr = open(logname, 'a')

        # Custom levels
        logging.addLevelName(45, 'EXCEPTION')
        logging.EXCEPTION = 45
        funcToMethod(exception, logging.Logger)

        logging.addLevelName(5, 'VERYVERBOSE')
        logging.VERYVERBOSE = 5
        funcToMethod(veryverbose, logging.Logger)

        logging.addLevelName(3, 'VERYVERYVERBOSE')
        logging.VERYVERYVERBOSE = 3
        funcToMethod(veryveryverbose, logging.Logger)

        #fmt = logging.Formatter("%(levelname)-8s %(asctime)s %(name)s %(message)s")
        #fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s","%x %X")
        fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
        if bytes:
            hdlr = logging.handlers.RotatingFileHandler(logname, "a", bytes, 3)  # Max bytes, 3 rotations
        else:
            # midnight
            #hdlr = logging.handlers.TimedRotatingFileHandler(logname, when='D', interval=1, backupCount=5)
            hdlr = LoggingTimedRotatingFileHandler(logname, when='D', interval=1, backupCount=5)
        hdlr.setFormatter(fmt)

        self.logger = logging.getLogger(loggername)
        self.logger.setLevel(eval("logging." + log_level))                     # Set logging level
        self.logger.addHandler(hdlr)

        #print logging.getLevelName(5)
        #print logging.getLevelName(3)

    def getLogger(self):
        return self.logger 


if __name__ == '__main__':
    """
    Note: There is a bug with logger.exception("This is an exception") => A None entry is added in the log???
    """

    bad = "This is BAD!"

    logger = Logger(r"j:\_DevTemp\foo.log", "VERYVERYVERBOSE", "CMISX")
    logger = logger.getLogger()

    """
    logger.debug("Ceci est un debug")
    logger.info("Ceci est un info")
    logger.warning("Ceci est un warning!")
    logger.error("Ceci est un error!")
    logger.critical("Ceci est un critical!")
    """

    #logger.log(5, "Ceci est veryverbose")
    #logger.log(logging.DEBUG, "Ceci est un debug")

    while True:
        logger.exception("Ceci est exception")
        logger.veryverbose("Ceci est veryverbose")
        logger.veryveryverbose("Ceci est veryveryverbose")
        time.sleep(1)

    """
    try:
        raise bad

    except bad:
        print "Bad exception has been raised"
        logger.exception("Ceci est un exception!")
    """